{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "30cfEID3_RSw"
   },
   "source": [
    "# CUDA C/C++ 통합 메모리(Unified Memory)와 nvprof을 이용한 가속 애플리케이션 메모리 관리"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "P5vZ_qXu_kb1"
   },
   "source": [
    "![CUDA](./images/CUDA_Logo.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "h62y_-VR_T_t"
   },
   "source": [
    "\n",
    "\n",
    "본 강좌와 다른 CUDA 기초 강좌의 후속 학습 자료로서 강력히 추천하는 [CUDA 베스트 프렉티스 가이드](http://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#memory-optimizations)에서는 **APOD**(**A**sess 평가, **P**arallelize 병렬화, **O**ptimize 최적화, **D**eploy 배포)라고 불리우는 디자인 사이클을 추천하고 있습니다. 간단히 말해서 APOD는 반복적 디자인 프로세스를 규정하는데, 개발자들은 애플리케이션 성능을 가속화하고 코드를 배포하는 데에 있어 점진적 개선 방식을 적용할 수 있습니다. 개발자들이 점점 유능한 CUDA 프로그래머가 되어감에 따라 더 진보한 최적화 기술을 가속화 코드베이스에 적용할 수 있게 됩니다.\n",
    "\n",
    "본 강좌에서는 이러한 반복적 개발 방법론을 이용할 것입니다. 여러분은 **NVIDIA 커맨드라인 프로파일러**를 사용하여 애플리케이션의 성능을 정량적으로(역주: 원문에는 qualitatively이나 quantitative의 오기인 듯) 측정하고, 최적화 기회를 포착하며, 새로운 기법을 배우고 사이클을 반복하기 전에 점진적 개선을 적용하게 될 것입니다. 본 강좌의 핵심은 여러분이 배우고 적용할 다수의 기법들이 CUDA의 **통합 메모리(Unified Memory; UM)**의 동작에 대한 것이라는 점입니다. CUDA 개발자들에게 통합 메모리의 동작 이해는 필수적이며 다른 많은 메모리 관리 기법을 이해하기 위한 발판이 됩니다.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "T7HO4ZpT_VPa"
   },
   "source": [
    "---\n",
    "\n",
    "## 선행지식\n",
    "\n",
    "강좌의 이해를 위하여 여러분은 다음 사항을 알고 있어야 합니다.\n",
    "\n",
    "* CPU 함수 호출과 GPU 커널 구동을 포함한 C/C++ 프로그램의 작성, 컴파일, 실행하기\n",
    "* 실행 설정을 이용한 병렬 스레드 계층구조 제어하기\n",
    "* 직렬 반복문을 GPU에서 병렬로 실행하도록 코드 수정하기\n",
    "* 통합 메모리의 할당과 해제\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "UGdeFq3N_VXd"
   },
   "source": [
    "---\n",
    "\n",
    "## 학습목표\n",
    "\n",
    "강좌를 마치면 여러분은 다음 사항을 수행할 수 있게 될 것입니다.\n",
    "\n",
    "* **NVIDIA 커맨드라인 프로파일러(nvprof)**를 이용하여 가속화 애플리케이션의 성능 프로파일링하기\n",
    "* 실행 설정 최적화를 위한 **스트리밍 멀티프로세서**에 대한 체계적 이해\n",
    "* 페이지 폴트와 데이터 마이그레이션에 관련된 **통합 메모리**의 동작 이해\n",
    "* 페이지 폴트와 데이터 마이그레이션을 감소시켜 성능을 향상시키는 **비동기적 메모리 프리패칭** 사용\n",
    "* 반복적 개발 사이클을 적용하여 애플리케이션 가속화와 배치를 신속히 진행하기\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "e3Vre6Kk_VaG"
   },
   "source": [
    "---\n",
    "\n",
    "## NVIDIA 프로파일러를 활용한 최적화\n",
    "\n",
    "\n",
    "### 실습: nvprof을 이용하여 애플리케이션 프로파일링 하기\n",
    "\n",
    "가속화 코드 베이스의 최적화 시도가 실제로 성공했는가를 확인하는 방법 중의 하나로서 성능과 관련된 정량적 정보를 프로파일링하는 방법이 있습니다. **`nvprof`**는 이러한 작업을 지원하는 NVIDIA의 커맨드라인 프로파일러입니다. CUDA 툴킷과 함께 배포되는 이것은 가속화 애플리케이션을 프로파일링하는 강력한 도구입니다.\n",
    "\n",
    "`nvprof`를 사용하기는 쉽습니다. 가장 기본적인 사용법은 `nvcc`로 컴파일한 실행 파일의 경로를 전달해 주는 것입니다. `nvprof`는 해당 애플리케이션을 실행한 후 GPU 활동, CUDA API 호출 기록, **통합 메모리(Unified Memory)** 활동 정보 등을 정리하여 출력해 줍니다. 자세한 내용은 강좌를 진행하며 다루도록 하겠습니다.\n",
    "\n",
    "애플리케이션을 가속화하거나 이미 가속화된 애플리케이션을 최적화할 때에 과학적이고 반복적인 접근법을 취하세요. 애플리케이션 변경 후에는 프로파일링을 수행하고, 기록을 남기고, 수정사항이 성능에 미치는 의미를 적으세요. 보다 이른 단계에, 그리고 자주 이러한 관찰을 수행하세요. 작은 노력들이 모여 성능을 획기적으로 향상시키고 출시를 돕는 경우가 많이 있습니다. 빈번한 프로파일링은 특정한 코드 변경이 실제 성능에 어떠한 영향을 미치는가를 여러분에게 가르쳐 줄 것입니다. 이러한 지식은 코드 베이스를 한참 변경한 후에 수행하는 프로파일링으로는 얻을 수 없는 중요한 자산입니다.  \n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "2Xog4F23_Vcx"
   },
   "source": [
    "### 실습: nvprof을 이용하여 애플리케이션 프로파일링 하기\n",
    "\n",
    "[`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)(<---- 소스 파일을 새로운 탭에서 열고 수정하려면 클릭하세요.)는 고속화 벡터합 프로그램입니다. 아래 두 개의 코드 실행 셀을 사용하세요. (`CTRL+ENTER`를 누르면 됩니다.) 첫 번째 코드 실행 셀은 벡터합 프로그램을 컴파일하고 실행합니다. 두 번째 셀은 방금 컴파일한 실행 파일을 `nvprof`을 이용하여 프로파일링합니다.\n",
    "\n",
    "애플리케이션의 프로파일링을 수행한 후, 출력에 나온 정보를 이용하여 아래 질문에 답하세요.\n",
    "\n",
    "* 여기서 호출된 유일한 CUDA 커널의 이름은 무엇인가요?\n",
    "* 커널은 몇 번 실행됐나요?\n",
    "* 커널이 실행되는 데 얼마나 걸렸나요? 시간을 기록해 두세요. 나중에 최적화를 수행하면 얼마나 빨라졌는지 알고 싶으실 것입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "P4jJaN69_RSx"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o single-thread-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "g1KAqEE-_RS2"
   },
   "outputs": [],
   "source": [
    "!nvprof ./single-thread-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "0ykx_M9M_RS6"
   },
   "source": [
    "### 실습: 최적화와 프로파일링 하기\n",
    "\n",
    "[`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)가 단일 스레드 블록상의 다수의 스레드에서 실행되도록 실행 설정을 수정하세요. 아래 코드 실행 셀에서 재컴파일하고 `nvprof`을 이용하여 실행 파일을 프로파일링하세요. 프로파일링 출력을 이용하여 커널의 실행 시간을 찾아 보세요. 최적화를 통한 속도 향상이 어느 정도인가요? 결과를  기록하세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "DuRhzAgi_RS7"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o multi-thread-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "cnCW2sYp_RS-"
   },
   "outputs": [],
   "source": [
    "!nvprof ./multi-thread-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6SkXoVFh_RTC"
   },
   "source": [
    "### 실습: 반복적 최적화 하기\n",
    "\n",
    "본 실습에서 여러분은 몇 차례에 걸쳐 [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)의 실행 설정을 수정, 프로파일링, 기록함으로써 성능에 미치는 영향을 살펴 볼 것입니다. 아래 가이드라인을 따르세요.\n",
    "\n",
    "* 실행 설정을 업데이트할 3~5 가지 방법을 나열하되, 다양한 범위의 그리드 및 블록 크기 조합을 사용하세요.\n",
    "* [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)를 수정하여 나열된 방법들을 하나씩 시도하세요.\n",
    "* 아래 셀을 이용하여 컴파일과 프로파일링을 수행하세요.\n",
    "* 프로파일링 출력에 주어진 실행 시간을 기록하세요.\n",
    "* 나열된 방법들 각각에 대한 수정/프로파일/기록 사이클을 수행하세요.\n",
    "\n",
    "어느 실행 설정이 가장 빠른 결과를 보였나요?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "R5q74gJB_RTD"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o iteratively-optimized-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "fIfWJW2h_RTH"
   },
   "outputs": [],
   "source": [
    "!nvprof ./iteratively-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "XBJZ0mr0_RTK"
   },
   "source": [
    "---\n",
    "\n",
    "## 스트리밍 멀티프로세서와 디바이스 질의하기\n",
    "\n",
    "본 섹션에서는 GPU 하드웨어의 특정 기능을 이해하고 이것을 최적화에 활용하는 법을 살펴 봅니다. 먼저 **스트리밍 멀티프로세서**를 살펴 보고 앞에서 작업한 가속 벡터합 프로그램을 좀 더 최적화해 봅니다.\n",
    "\n",
    "다음 슬라이드는 앞으로 공부할 내용을 개략적인 수준에서 시각적으로 보여줍니다. 주제를 보다 상세히 다루기에 앞서 슬라이드를 클릭하시면서 살펴 보세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 591
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 840,
     "status": "ok",
     "timestamp": 1541021683886,
     "user": {
      "displayName": "Hyungon Ryu KR",
      "photoUrl": "https://lh5.googleusercontent.com/-wtRHVbivPO4/AAAAAAAAAAI/AAAAAAAAABk/eM5PPjT2Row/s64/photo.jpg",
      "userId": "08585482373097815411"
     },
     "user_tz": -540
    },
    "id": "CcRZmRBe_RTL",
    "outputId": "faa8d69e-c66d-4b9b-cfef-8a587a8a2fc3"
   },
   "outputs": [],
   "source": [
    "%%HTML\n",
    "\n",
    "<iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vRByDOlhmGKNY9IgFonAhE-uM0NAPdZGo8v8vlBBPqRB7RDx-E5g0OnGOpC2VoO-eWFhZBWv5yCtGfk/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8Civl39l_RTQ"
   },
   "source": [
    "---\n",
    "### 스트리밍 멀티프로세서와 워프\n",
    "\n",
    "CUDA 애플리케이션을 실행하는 GPU는 **스트리밍 멀티프로세서(streaming multiprocessor; SM)**라는 프로세싱 유닛을 가지고 있습니다. 최대한 많은 수의 병렬처리를 위하여 GPU에 있는 *SM 숫자의 배수로 이루어진 블록수를 그리드의 크기로 선택*함으로써 성능 이득을 얻을 수 있습니다.\n",
    "\n",
    "SM은 하나의 블록 안에 있는 32 개의 스레드를 하나의 그룹처럼 다루어 스레드 생성, 관리, 스케쥴링, 실행을 합니다. 이러한 32 개의 스레드로 이루어진 그룹을 **워프(warps)**라고 합니다. [SM과 워프에 대한 상세 내용](http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#hardware-implementation)은 본 강좌의 범위를 벗어나지만 *32의 배수를 스레드 갯수로 갖는 블록 크기를 선택*함으로써 성능 이득을 얻을 수 있다는 것을 기억하는 것은 중요합니다. \n",
    "\n",
    "### 프로그램으로 GPU 디바이스 속성 질의하기\n",
    "\n",
    "GPU 상의 SM 갯수는 GPU 모델에 땨라 다르기 때문에, 서로 다른 SM 갯수를 가진 GPU 간의 프로그램 이식성을 유지하기 위하여 SM 갯수는 코드 베이스에 하드코드되어서는 안됩니다. 이 정보는 프로그램으로 얻어내야 합니다.\n",
    "\n",
    "아래 예제는 CUDA C/C++에서, SM 값을 포함하여, 현재 활성화된 GPU의 다양한 속성을 알아내는 데 사용되는 C 구조체를 얻는 방법을 보여줍니다. \n",
    "\n",
    "```cpp\n",
    "int deviceId;\n",
    "cudaGetDevice(&deviceId);                  // `deviceId` now points to the id of the currently active GPU.\n",
    "\n",
    "cudaDeviceProp props;\n",
    "cudaGetDeviceProperties(&props, deviceId); // `props` now has many useful properties about\n",
    "                                           // the active GPU device.\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "0JufkGBzBGfO"
   },
   "source": [
    "### 실습: 디바이스 질의하기\n",
    "\n",
    "[`01-get-device-properties.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/04-device-properties/01-get-device-properties.cu)는 다수의 초기화되지 않은 변수를 포함하고 있기 때문에, 현재 활성화된 GPU의 상세한 속성을 표시해야 하는 부분에서 의미 없는 값을 출력하고 있습니다. \n",
    "\n",
    "[`01-get-device-properties.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/04-device-properties/01-get-device-properties.cu) 코드를 수정하여 소스 코드가 의도했던 바대로 디바이스 속성의 실제값을 출력하도록 만드세요. [CUDA 런타임 문서](http://docs.nvidia.com/cuda/cuda-runtime-api/structcudaDeviceProp.html)를 읽어 보시면 실습을 진행하는 데 필요한 도움을 얻을 수 있습니다. 어떻게 해야 할 지 모르는 경우에는 [솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/04-device-properties/solutions/01-get-device-properties-solution.cu)을 참고하세요. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "53YQ_JP9_RTR"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o get-device-properties 04-device-properties/01-get-device-properties.cu -run"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6x6FdbCW_RTW"
   },
   "source": [
    "### 실습: SM 숫자에 맞춘 크기의 그리드를 사용한 벡터합 최적화\n",
    "\n",
    "앞에서 배운 SM 갯수 질의 기능을 이용하여 [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)의 `addVectorsInto` 커널을 수정하되, 이를 SM 갯수의 배수에 해당하는 블럭을 포함한 그리드로 구동되게 하세요.\n",
    "\n",
    "여러분이 작성하는 코드의 세부 내용에 따라서 수정 사항이 커널 성능 개선을 이룰 수도, 이루지 않을 수도 있습니다. `nvprof`를 이용하여 성능 변화를 정량적으로 측정하고 그 결과를 통해 발견한 사항을 기록하세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "e0zJk4Gm_RTX"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o sm-optimized-vector-add 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "WqupE6Yn_RTa"
   },
   "outputs": [],
   "source": [
    "!nvprof ./sm-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8eB2tPlw_RTf"
   },
   "source": [
    "---\n",
    "\n",
    "## 통합 메모리(Unified Memory) 세부 내용\n",
    "\n",
    "여러분은 `cudaMallocManaged` 함수를 이용하여 호스트와 디바이스 코드가 사용할 메모리를 할당해 왔습니다. 지금까지 이 함수가 제공하는 자동 메모리 마이그레이션, 쉬운 프로그래밍과 같은 혜택을 이용해 오면서도 `cudaMallocManaged`가 실제로 할당하는 **통합 메모리(unified memeory; UM)**에 대한 세부 내용은 신경 쓸 필요가 없었습니다. `nvprof`는 가속화 애플리케이션의 UM 관리에 대한 상세한 정보를 제공하는데, 이 정보와 아울러 UM의 작동 원리를 보다 잘 이해하게 된다면 가속 애플리케이션 최적화의 추가적인 기회 얻을 수 있습니다.  \n",
    "\n",
    "다음 슬라이드는 앞으로 공부할 내용을 개략적인 수준에서 시각적으로 보여줍니다. 주제를 보다 상세히 다루기에 앞서 슬라이드를 클릭하시면서 살펴 보세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 591
    },
    "colab_type": "code",
    "executionInfo": {
     "elapsed": 1087,
     "status": "ok",
     "timestamp": 1541021661332,
     "user": {
      "displayName": "Hyungon Ryu KR",
      "photoUrl": "https://lh5.googleusercontent.com/-wtRHVbivPO4/AAAAAAAAAAI/AAAAAAAAABk/eM5PPjT2Row/s64/photo.jpg",
      "userId": "08585482373097815411"
     },
     "user_tz": -540
    },
    "id": "TYxq2B8T_RTg",
    "outputId": "33a80054-d57a-438c-c223-eeae512082f8"
   },
   "outputs": [],
   "source": [
    "%%HTML\n",
    "\n",
    "<iframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vTasuq4eIe8Xd_G-xL-dD6hbkv48C_8xD4WS1780qnWnidDc5FApS--f86luAU5uM5IlJiiAhBAH4v-/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"900\" height=\"550\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\"></iframe>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "ybH9kfZV_RTk"
   },
   "source": [
    "### 통합 메모리(UM) 마이그레이션\n",
    "\n",
    "UM이 할당될 때, 메모리는 아직 호스트 또는 디바이스에 적재되지 않습니다. 호스트나 디바이스가 그 메모리에 접근하려고 하면 [페이지 폴트](https://en.wikipedia.org/wiki/Page_fault)가 일어나고 이 시점에 호스트와 디바이스는 필요한 데이터를 연속적으로 읽어들여 옵니다. 이것을 메모리 마이그레이션(memory migration)이라고 합니다. 이와 마찬가지로 호스트나 디바이스가 아직 적재되지 않은 메모리에 접근을 시도한다면 페이지 폴트가 일어나고 마이그레이션이 시작됩니다.\n",
    "\n",
    "페이지 폴트와 요청시 마이그레이션은 가속화 애플리케이션 개발을 쉽게 해주어 큰 도움이 됩니다. 특히 애플리케이션이 실제로 실행되어 데이터를 필요로 할 때까지 어느 데이터가 필요한지 알 수 없는 경우가 있는데, 이와 같이 흩어진 데이터를 다루는 경우, 또는 다수의 GPU가 접근하는 데이터와 같은 경우에 있어서 요청시 마이그레이션은 매우 유용합니다.\n",
    "\n",
    "하지만 어떤 데이터가 필요한지 미리 알 수 있는 경우와 큰 연속 메모리 영역이 필요한 경우도 많이 있습니다. 이 경우에는 오히려 페이지 폴트와 요청시 마이그레이션의 오버헤드가 큰 부담이 되므로 이러한 오버헤드 비용은 피하는 것이 좋습니다.\n",
    "\n",
    "본 강좌의 나머지 부분은 요청시 마이그레이션을 이해하고 프로파일러 출력에서 이를 확인하는 것에 대해 할애할 것입니다. 이러한 지식을 통하여 여러분은 오버헤드를 피하는 것이 유리한 경우를 이해하고 대처할 수 있게 될 것입니다.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "F1SEmwzGBdeK"
   },
   "source": [
    "### 실습: UM 페이지 폴트 살펴 보기\n",
    "\n",
    "`nvprof`는 대상 애플리케이션의 UM 작동 상황을 출력합니다. 본 실습에서 여러분은 간단한 애플리케이션을 수정해 가면서, 각 수정에 대한 `nvprof`의 UM 출력 부분을 이용하여 UM 데이터 마이그레이션 행동을 살펴볼 것입니다.\n",
    "\n",
    "[`01-page-faults.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/01-page-faults.cu)은 `hostFunction`과 `gpuKernel`을 포함하는데 이들은 2<<24 개의 원소를 가지는 벡터의 원소값을 1로 초기화하는 역할을 합니다. 아직은 이들 호스트 함수와 디바이스 커널은 사용되지 않습니다. 아래 네 개의 질문에 대하여 여러분이 UM 동작에 대해 배운 것을 기반으로 하여 어떤 종류의 페이지 폴트가 일어날지 가정해 보새요. 다음으로는 [`01-page-faults.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/01-page-faults.cu)를 수정하되, 앞에서 나온 함수들을 이용하여 여러분의 가정을 검증할 시나리오를 작성하세요.\n",
    "\n",
    "여러분의 가정을 검증하기 위해서 아래 셀을 이용하여 코드를 컴파일하고 프로파일링 하세요. 여러분의 가정과 함께 `nvprof`에서 얻은 결과를 기록하되, CPU와 GPU의 페이지 폴트에 집중하며 네 개의 실험을 수행하세요. 어떻게 해야 할 지 모를 때에는 질문 옆에 있는 솔루션 링크를 참고하세요. \n",
    "\n",
    "* 통합 메모리가 CPU에 의해서만 사용될 때 무슨 일이 일어날까요? ([솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/solutions/01-page-faults-solution-cpu-only.cu))\n",
    "* 통합 메모리가 GPU에 의해서만 사용될 때 무슨 일이 일어날까요? ([솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/solutions/02-page-faults-solution-gpu-only.cu))\n",
    "* 통합 메모리가 CPU에 의해 먼저 사용되고 다음으로 GPU에 의해서 사용될 때 무슨 일이 일어날까요? ([솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/solutions/03-page-faults-solution-cpu-then-gpu.cu))\n",
    "* 통합 메모리가 GPU에 의해 먼저 사용되고 다음으로 CPU에 의해서 사용될 때 무슨 일이 일어날까요? ([솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/06-unified-memory-page-faults/solutions/04-page-faults-solution-gpu-then-cpu.cu))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "jgU_u5CT_RTm"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o page-faults 06-unified-memory-page-faults/01-page-faults.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "faCVxIx__RTq"
   },
   "outputs": [],
   "source": [
    "!nvprof ./page-faults"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "oLY_f8Hj_RTu"
   },
   "source": [
    "### 실습: 벡터합 프로그렘에서 UM 동작 다시 살펴보기\n",
    "\n",
    "\n",
    "이제 다시 [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)로 돌아오세요. 현재의 코드 베이스를 살펴 보고 어떤 종류의 페이지 폴트가 일어날지 생각해 보세요. 제일 마지막으로 코드를 수정했을 때의 프로파일링 결과를 살펴 보세요. 과거 출력을 찾아 보기 위해 위로 스크롤하시거나 아래 셀에서 명령을 다시 실행하면 됩니다. 통합 메모리의 프로파일 출력을 살펴 보세요. 현재 코드 베이스의 내용으로부터 프로파일의 페이지 폴트 결과 출력을 스스로 설명할 수 있으시겠어요?\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "tthpxx50_RTw"
   },
   "outputs": [],
   "source": [
    "!nvprof ./sm-optimized-vector-add"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Z1i9bvFo_RTz"
   },
   "source": [
    "### 실습: 커널에서 벡터 초기화\n",
    "\n",
    "`nvprof`가 얼마 동안 커널을 실행하여 HtoD(Host to Device) 페이지 폴트와 데이터 마이그레이션이 발생하면, 그 페이지 폴트와 마이그레이션 역시 실행 시간에 포함되어 출력됩니다. 이것을 염두하시면서 [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)의 `initWith` 호스트 함수를 수정하여 CUDA 커널로 만들고, 할당된 벡터를 GPU에서 병렬로 초기화하세요. 수정된 코드를 성공적으로 컴파일하고 실행한 뒤, 프로파일링에 앞서 다음 사항에 대한 가정을 수립해 보세요.\n",
    "\n",
    "* 코드 수정이 UM 페이지 폴트 동작에 어떤 영향을 미칠까요?\n",
    "* 코드 수정이 `addVectorsInto`의 실행 시간에 어떤 영향을 미칠까요?\n",
    "\n",
    "결과를 기록하세요. 어떻게 해야 할 지 모를 때에는 [솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/07-init-in-kernel/solutions/01-vector-add-init-in-kernel-solution.cu)을 참고하세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "rhuaffw1_RT0"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o initialize-in-kernel 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "GadlFwLQ_RT4"
   },
   "outputs": [],
   "source": [
    "!nvprof ./initialize-in-kernel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "9uXOmu2t_RT6"
   },
   "source": [
    "---\n",
    "## 비동기 메모리 프리패칭\n",
    "\n",
    "페이지 폴트와 호스트에서 디바이스로 또는 디바이스에서 호스트로의 메모리 전달인 요구시 메모리 마이그레이션으로 인한 오버헤드를 줄이는 강력한 기법을 **비동기 메모리 프리패칭(asynchronous memory prefetching)**이라고 합니다. 이 기법을 이용하면 애플리케이션이 메모리를 사용하려고 하기 전에 프로그래머가 UM을 임의의 CPU나 GPU로 마이그레이션할 수 있는데, 이는 비동기적으로 백그라운드에서 수행됩니다. 이를 통해 줄어든 페이지 폴트 및 요구시 마이그레이션만큼 GPU 커널과 CPU 함수의 성능이 향상됩니다.\n",
    "\n",
    "프리패칭은 데이터를 큰 덩어리로 마이그레이션하는 경향이 있어 요구시 마이그레이션보다 메모리 전달 빈도가 낮아집니다. 이는 접근할 데이터를 런타임 전에 알 수 있고, 데이터 접근 패턴이 산발적이지 않은 경우에 아주 적합합니다.\n",
    "\n",
    "CUDA는 `cudaMemPrefetchAsync` 함수를 이용하여 메모리에서 GPU/CPU로의 비동기적 프리패칭을 손쉽게 처리합니다. 아래 예제는 현재 활성화된 GPU로 데이터를 프리패치한 후, CPU로 프리패치하는 방법을 보여 줍니다.\n",
    "\n",
    "```cpp\n",
    "int deviceId;\n",
    "cudaGetDevice(&deviceId);                                         // The ID of the currently active GPU device.\n",
    "\n",
    "cudaMemPrefetchAsync(pointerToSomeUMData, size, deviceId);        // Prefetch to GPU device.\n",
    "cudaMemPrefetchAsync(pointerToSomeUMData, size, cudaCpuDeviceId); // Prefetch to host. `cudaCpuDeviceId` is a\n",
    "                                                                  // built-in CUDA variable.\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "qvZ74TZnBxNi"
   },
   "source": [
    "### 실습: 메모리 프리패치하기\n",
    "\n",
    "지금쯤이면 여러분의 [01-vector-add.cu](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu) 프로그램은 두 개의 벡터를 더하여 세 번째 솔루션 벡터에 대입하는 CUDA 커널을 구동하며, 벡터들은 모두 `cudaMallocManaged`를 이용하여 할당되어 있을 것입니다. 그뿐만 아니라, 3 개의 벡터들은 모두 CUDA 커널에서 병렬로 초기화되어 있을 것입니다. 만약 여러분의 코드가 그렇게 되어 있지 않다면 [참조 코드](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/08-prefetch/01-vector-add-prefetch.cu)를 보시고 여러분의 코드를 수정하세요. \n",
    "\n",
    "여러분의 [`01-vector-add.cu`](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/01-vector-add/01-vector-add.cu)에서 `cudaMemPrefetchAsync`를 이용하고, 이 함수가 페이지 폴트와 메모리 마이그레이션에 미치는 영향을 세 가지 실험을 통해 알아 보세요. \n",
    "\n",
    "* 초기화된 벡터 중 하나를 호스트로 프리패치하면 어떤 일이 벌어지나요?\n",
    "* 초기화된 벡터 중 두 개를 호스트로 프리패치하면 어떤 일이 벌어지나요?\n",
    "* 초기화된 벡터 세 개 모두를 호스트로 프리패치하면 어떤 일이 벌어지나요?\n",
    "\n",
    "UM의 동작, 특히 페이지 폴트에 대한 가정과 커널 초기화 실행 시간에 미치는 영향에 대한 가정을 수립한 후, `nvprof`를 이용하여 실험을 하고 가정을 검증하세요. 어떻게 해야 할 지 모를 때에는 [솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/08-prefetch/solutions/01-vector-add-prefetch-solution.cu)을 참고하세요."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Wq_PCpCi_RT-"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o prefetch-to-gpu 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "ujHhJTwa_RUB"
   },
   "outputs": [],
   "source": [
    "!nvprof ./prefetch-to-gpu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "njvx92qI_RUE"
   },
   "source": [
    "### 실습: CPU로 메모리 프리패치하기 \n",
    "\n",
    "`addVectorIntoAdd` 커널이 잘 동작하는지 검증하는 함수를 위해 CPU로의 프리패칭을 추가하세요. `nvprog`를 이용하여 결과를 확인하기 전에 UM에 미치는 영향에 대한 가정을 수립하세요. 어떻게 해야 할 지 모를 때에는 [솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/08-prefetch/solutions/02-vector-add-prefetch-solution-cpu-also.cu)을 참고하세요.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "9tjTExP__RUF"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o prefetch-to-cpu 01-vector-add/01-vector-add.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "aOBmgu76_RUJ"
   },
   "outputs": [],
   "source": [
    "!nvprof ./prefetch-to-cpu"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Geow9BAd_RUN"
   },
   "source": [
    "---\n",
    "## 요약\n",
    "\n",
    "이제 여러분은 아래와 같은 학습 목표를 달성하셨습니다.\n",
    "\n",
    "* **NVIDIA 커맨드라인 프로파일러(nvprof)**를 이용하여 가속화 애플리케이션의 성능 프로파일링하기\n",
    "* 실행 설정 최적화를 위한 **스트리밍 멀티프로세서**에 대한 체계적 이해\n",
    "* 페이지 폴트와 데이터 마이그레이션에 관련된 **통합 메모리**의 동작 이해\n",
    "* 페이지 폴트와 데이터 마이그레이션을 감소시켜 성능을 향상시키는 **비동기적 메모리 프리패칭** 사용\n",
    "* 반복적 개발 사이클을 적용하여 애플리케이션 가속화와 배치를 신속히 진행하기\n",
    "\n",
    "여러분의 지식을 공고히 하고 애플리케이션의 반복적 가속화/최적화/배치에 대한 여러분의 능력을 향상시키기 위해 강좌의 마지막 실습을 수행하세요. 완료한 후에는 가급적 *고급 주제* 섹션도 진행해 보세요. (역주: 고급 주제 섹션 없슴.)\n",
    "\n",
    "---\n",
    "\n",
    "## 마지막 실습: 가속화 SAXPY 애플리케이션의 반복적 최적화\n",
    "\n",
    "기초적인 가속화 [SAXPY](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_1) 애플리케이션이 [여기](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/09-saxpy/01-saxpy.cu)에 준비되어 있습니다. 이 코드는 현재 몇 가지 버그가 있습니다. 이것들을 찾아 수정하고 실행한 뒤 `nvprof`을 이용하여 프로파일링하세요.\n",
    "\n",
    "다 되셨으면 `saxpy` 커널의 실행 시간을 기록하고 **반복적으로** 최적화를 진행하세요. 반복할 때마다 `nvprof`을 이용하여 코드 수정이 커널 성능과 UM 동작에 미치는 영향을 확인하세요.\n",
    "\n",
    "강좌에서 배운 기법을 이용해 보세요. 강죄에서 배운 기법을 그냥 교재에서 찾아 보기만 하지 마시고 [의도적 기억인출(effortful retrieval)](http://sites.gsu.edu/scholarlyteaching/effortful-retrieval/) 기법을 활용하시면 학습에 도움이 됩니다. \n",
    "\n",
    "여러분의 목표는 올바른 saxpy 커널을 프로파일링하되, `N`을 바꾸지 않은 상태에서 *50us* 내에 실행을 완료하는 것입니다. 어떻게 해야 할 지 모를 때에는 [솔루션](../../../../../edit/tasks/task1/task/02_AC_UM_NVPROF-kr/09-saxpy/solutions/02-saxpy-solution.cu)을 참고하시고 자유롭게 컴파일하고 프로파일링 해보셔도 됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "XSw8KUsb_RUO"
   },
   "outputs": [],
   "source": [
    "!nvcc -arch=sm_70 -o saxpy 09-saxpy/01-saxpy.cu -run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "yFumLOe-_RUS"
   },
   "outputs": [],
   "source": [
    "!nvprof ./saxpy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "qcleQ5g0CVfv"
   },
   "source": [
    "![CUDA](./images/CUDA_Logo.jpg)"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "02_AC_UM_NVPROF-kr.ipynb.ipynb",
   "provenance": [],
   "version": "0.3.2"
  },
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
